#include <stdio.h>
#include <string.h>
#include "MEM.h"
#include "DBG.h"
#include "wuji.h"
#include "WJ_dev.h"

#define LINE_BUF_SIZE		(1024)

typedef enum {
	FOPEN_ARGUMENT_TYPE_ERR = 0,
	FCLOSE_ARGUMENT_TYPE_ERR,
	FGETS_ARGUMENT_TYPE_ERR,
	FILE_ALREADY_CLOSED_ERR,
	FPUTS_ARGUMENT_TYPE_ERR,
	NEW_ARRAY_ARGUMENT_TYPE_ERR,
	NEW_ARRAY_ARGUMENT_TOO_FEW_ERR,
	EXIT_ARGUMENT_TYPE_ERR,
	NEW_EXCEPTION_ARGUMENT_ERR,
	FGETS_BAD_MULTIBYTE_CHARACTER_ERR
} NativeErrirCode;

extern ErrorMessageFormat wj_native_error_message_format[];

/* static WJ_NativePointerInfo st_lib_info = { */
	/* wj_native_error_message_format, */
/* }; */

static void file_finalizer(WJ_Interpreter *inter, WJ_Object *obj);

static WJ_NativePointerInfo st_file_type_info = {
    "wuji.lang.file",
	file_finalizer
};

static void file_finalizer(WJ_Interpreter *inter, WJ_Object *obj)
{
	FILE *fp;

	fp = (FILE*)WJ_object_get_native_pointer(obj);
	if (fp && fp != stdin && fp != stdout && fp != stderr) {
		fclose(fp);
	}
	return;
}

static void
check_argument_count(int arg_count, int true_count)
{


    if (arg_count < true_count) {
        wj_runtime_error(0, ARGUMENT_TOO_FEW_ERR,
                          MESSAGE_ARGUMENT_END);
    } else if (arg_count > true_count) {
        wj_runtime_error(0, ARGUMENT_TOO_MANY_ERR,
                          MESSAGE_ARGUMENT_END);
    }
}

WJ_Value
wj_nv_print_proc(WJ_Interpreter *interpreter,
                  WJ_LocalEnvironment *env,
                            int arg_count, WJ_Value *args)
{
    WJ_Value value;
    char *str;

    value.type = WJ_NULL_VALUE;

    check_argument_count(arg_count, 1);
    str = WJ_value_to_string(&args[0]);
    printf("%s\n", str);
    MEM_free(str);

    return value;
}


WJ_Value
wj_nv_print_proc_debug(WJ_Interpreter *interpreter,
                  WJ_LocalEnvironment *env,
                            int arg_count, WJ_Value *args)
{
    WJ_Value value;
    char *str;

    value.type = WJ_NULL_VALUE;

    check_argument_count(arg_count, 1);
    str = WJ_value_to_string(&args[0]);
    printf("%s", str);
    MEM_free(str);

    return value;
}

static void 
check_file_pointer(WJ_Interpreter *inter, WJ_LocalEnvironment *env,
					WJ_Object *obj)
{
	FILE *fp;

	fp = (FILE*)WJ_object_get_native_pointer(obj);
	if (fp == NULL) {
		wj_runtime_error(__LINE__, (int)FILE_ALREADY_CLOSED_ERR, MESSAGE_ARGUMENT_END);
	}
}

WJ_Value
wj_nv_fopen_proc(WJ_Interpreter *interpreter,
                  WJ_LocalEnvironment *env,
                            int arg_count, WJ_Value *args)
{
    WJ_Value value;
    FILE *fp;
	/* char *filename; */
	/* char *mode; */

    check_argument_count(arg_count, 2);

    if (args[0].type != WJ_STRING_VALUE
        || args[1].type != WJ_STRING_VALUE) {
        wj_runtime_error(__LINE__, FOPEN_ARGUMENT_TYPE_ERR,
                          MESSAGE_ARGUMENT_END);
    }
    
    fp = fopen(args[0].u.object->u.string.string,
               args[1].u.object->u.string.string);
    if (fp == NULL) {
        value.type = WJ_NULL_VALUE;
    } else {
        value.type = WJ_NATIVE_POINTER_VALUE;
		value.u.object = WJ_create_native_pointer(interpreter, env, fp, &st_file_type_info);
    }

    return value;
}


WJ_Value
wj_nv_fclose_proc(WJ_Interpreter *interpreter,
                   WJ_LocalEnvironment *env,
                             int arg_count, WJ_Value *args)
{
    WJ_Value value;
    FILE *fp;

    check_argument_count(arg_count, 1);

    value.type = WJ_NULL_VALUE;
    if (args[0].type != WJ_NATIVE_POINTER_VALUE
			|| (!WJ_check_native_pointer_type(args[0].u.object, &st_file_type_info))) {
        wj_runtime_error(__LINE__, FCLOSE_ARGUMENT_TYPE_ERR,
                          MESSAGE_ARGUMENT_END);
    }
	check_file_pointer(interpreter, env, args[0].u.object);
	fp = WJ_object_get_native_pointer(args[0].u.object);
    fclose(fp);
	WJ_object_set_native_pointer(args[0].u.object, NULL);

    return value;
}

WJ_Value
wj_nv_fgets_proc(WJ_Interpreter *interpreter,
                 WJ_LocalEnvironment *env,
                 int arg_count, WJ_Value *args)
{
    WJ_Value value;
    FILE *fp;
    char buf[LINE_BUF_SIZE];
    char *ret_buf = NULL;
    int ret_len = 0;

    check_argument_count(arg_count, 1);

    if (args[0].type != WJ_NATIVE_POINTER_VALUE
			|| (!WJ_check_native_pointer_type(args[0].u.object, &st_file_type_info))) {
        wj_runtime_error(0, FGETS_ARGUMENT_TYPE_ERR,
                          MESSAGE_ARGUMENT_END);
    }
	check_file_pointer(interpreter, env, args[0].u.object);
	fp = WJ_object_get_native_pointer(args[0].u.object);

    while (fgets(buf, LINE_BUF_SIZE, fp)) {
        int new_len;
        new_len = ret_len + strlen(buf);
        ret_buf = MEM_realloc(ret_buf, new_len + 1);
        if (ret_len == 0) {
            strcpy(ret_buf, buf);
        } else {
            strcat(ret_buf, buf);
        }
        ret_len = new_len;
        if (ret_buf[ret_len-1] == '\n')
            break;
    }
    if (ret_len > 0) {
        value.type = WJ_STRING_VALUE;
        value.u.object = WJ_create_wuji_string(interpreter, env, ret_buf);
    } else {
        value.type = WJ_NULL_VALUE;
    }

    return value;
}

WJ_Value
wj_nv_fputs_proc(WJ_Interpreter *interpreter,
                  WJ_LocalEnvironment *env,
                            int arg_count, WJ_Value *args)
{
    WJ_Value value;
    FILE *fp;

    check_argument_count(arg_count, 2);

    value.type = WJ_NULL_VALUE;
    if (args[0].type != WJ_STRING_VALUE
        || args[1].type != WJ_NATIVE_POINTER_VALUE
		|| (!WJ_check_native_pointer_type(args[1].u.object, &st_file_type_info))) {
        wj_runtime_error(0, FPUTS_ARGUMENT_TYPE_ERR,
                          MESSAGE_ARGUMENT_END);
    }
	check_file_pointer(interpreter, env, args[1].u.object);
	fp = WJ_object_get_native_pointer(args[1].u.object);

    fputs(args[0].u.object->u.string.string, fp);

    return value;
}

WJ_Value
new_array_sub(WJ_Interpreter *inter, WJ_LocalEnvironment *env,
              int arg_count, WJ_Value *args, int arg_idx)
{
    WJ_Value ret;
	WJ_Value value;
    int size;
    int i;

    if (args[arg_idx].type != WJ_INT_VALUE) {
        wj_runtime_error(0, NEW_ARRAY_ARGUMENT_TYPE_ERR,
                          MESSAGE_ARGUMENT_END);
    }

    size = args[arg_idx].u.int_value;

    ret.type = WJ_ARRAY_VALUE;
    ret.u.object = WJ_create_array(inter, env, size);

    if (arg_idx == arg_count-1) {
		value.type = WJ_NULL_VALUE;
        for (i = 0; i < size; i++) {
			WJ_array_set(inter, env, ret.u.object, i, &value);
        }
    } else {
        for (i = 0; i < size; i++) {
			value = new_array_sub(inter, env, arg_count, args, arg_idx + 1);
			WJ_array_set(inter, env, ret.u.object, i, &value);
        }
    }
    return ret;
}

WJ_Value
wj_nv_new_array_proc(WJ_Interpreter *interpreter,
                      WJ_LocalEnvironment *env,
                      int arg_count, WJ_Value *args)
{
    WJ_Value value;

    if (arg_count < 1) {
        wj_runtime_error(0, ARGUMENT_TOO_FEW_ERR,
                          MESSAGE_ARGUMENT_END);
    }
    value = new_array_sub(interpreter, env, arg_count, args, 0);
    return value;
}

void
add_native_functions(WJ_Interpreter *inter)
{
	WJ_add_native_function(inter, "打印",		wj_nv_print_proc);			/* printf */
	WJ_add_native_function(inter, "调试",		wj_nv_print_proc_debug);
    WJ_add_native_function(inter, "打开文件",	wj_nv_fopen_proc);			/* fopen */
    WJ_add_native_function(inter, "关闭文件",	wj_nv_fclose_proc);			/* fclose */
    WJ_add_native_function(inter, "读一行文件数据", wj_nv_fgets_proc);		/* fgets */
    WJ_add_native_function(inter, "写字符串到文件", wj_nv_fputs_proc);		/* fputs */
    WJ_add_native_function(inter, "新建数组",	wj_nv_new_array_proc);		/* new_array */
}



void
wj_add_std_fp(WJ_Interpreter *inter)
{
    WJ_Value fp_value;

    fp_value.type = WJ_NATIVE_POINTER_VALUE;
	fp_value.u.object = wj_create_native_pointer_i(inter, stdin, &st_file_type_info);
    WJ_add_global_variable(inter, "STDIN", &fp_value, WJ_TRUE);

	fp_value.u.object = wj_create_native_pointer_i(inter, stdout, &st_file_type_info);
    WJ_add_global_variable(inter, "STDOUT", &fp_value, WJ_TRUE);

	fp_value.u.object = wj_create_native_pointer_i(inter, stderr, &st_file_type_info);
    WJ_add_global_variable(inter, "STDERR", &fp_value, WJ_TRUE);
}


